﻿using System;
using System.Text;
using FRL.DebtChased.Silverlight.Library.RichContentService;

namespace FRL.DebtChased.Silverlight.Library
{
    public class Money2
    {

        /////////////////////////////////////////////////////////////
        // Constants

        private const string GBP_Symbol = "£";
        private const string USD_Symbol = "$";
        private const string Euro_Symbol = "€";
        private const string Yen_Symbol = "¥";

        public const string GBP_Code = "GBP";
        public const string USD_Code = "USD";
        public const string Euro_Code = "EUR";
        public const string Yen_Code = "JPY";

        public const string DefaultCurrency = GBP_Code;
        public const string Mixed_Code = "#";
        public const string Unknown_Code = "???";

        /////////////////////////////////////////////////////////////
        // Attributes

        private decimal _amount;
        public decimal Amount
        {
            get { return _amount; }
            set { _amount = value; }
        }

        private string _currencyCode;
        public string CurrencyCode
        {
            get { return _currencyCode; }
            set { this._currencyCode = CurrencySymbolToCode(value); }
        }

        public string CurrencySymbol
        {
            get
            {
                if (CurrencyCode == GBP_Code) return GBP_Symbol;
                if (CurrencyCode == USD_Code) return USD_Symbol;
                if (CurrencyCode == Euro_Code) return Euro_Symbol;
                if (CurrencyCode == Yen_Code) return Yen_Symbol;
                return CurrencyCode;
            }
        }

        /////////////////////////////////////////////////////////////
        // Construction

        public Money2(Money2 source)
        {
            this._amount = source._amount;
            this._currencyCode = source.CurrencyCode;
        }

        public Money2(decimal amount, string currencyCode)
        {
            this._amount = amount;
            this.CurrencyCode = currencyCode;
        }

        public Money2(CurrencyTransport ct)
        {
            this._amount = ct.Amount;
            this.CurrencyCode = ct.CurrencyCode;
        }

        public Money2(decimal amount)
        {
            this.Amount = amount;
            this.CurrencyCode = GBP_Code;
        }

        /////////////////////////////////////////////////////////////////
        #region Implementation

        public override string ToString()
        {
            return Format(true);
        }

        public override bool Equals(object obj)
        {
            Money2 mn = (Money2)obj;
            if (object.Equals(mn, null))
                return false;
            return ((mn.CurrencyCode == this.CurrencyCode) && (mn.Amount == this.Amount));
        }

        public override int GetHashCode()
        {
            return this.ToString().GetHashCode();
        }

        public static bool operator ==(Money2 mn1, Money2 mn2)
        {
            if (object.Equals(mn1, null))
                return object.Equals(mn2, null);
            else
                return mn1.Equals(mn2);
        }

        public static bool operator !=(Money2 mn1, Money2 mn2)
        {
            if (object.Equals(mn1, null))
                return (!object.Equals(mn2, null));
            else
                return !mn1.Equals(mn2);
        }

        public static Money2 operator +(Money2 mn1, Money2 mn2)
        {
            if (mn1 == null) return mn2;
            else if (mn2 == null) return mn1;
            else return (new Money2(mn1.Amount + mn2.Amount, (mn1.CurrencyCode == mn2.CurrencyCode) ? mn1.CurrencyCode : Mixed_Code));
        }

        public static Money2 operator -(Money2 mn1, Money2 mn2)
        {
            if (mn1 == null) return mn2;
            else if (mn2 == null) return mn1;
            else return (new Money2(mn1.Amount - mn2.Amount, (mn1.CurrencyCode == mn2.CurrencyCode) ? mn1.CurrencyCode : Mixed_Code));
        }

        public static implicit operator Money2(CurrencyTransport ct)
        {
            if (ct == null) return null;
            return new Money2(ct);
        }
        public static explicit operator CurrencyTransport(Money2 m)
        {
            if (m == null) return null;
            if (!IsValidCurrencyCode(m.CurrencyCode))
                throw new Exception("Invalid currency code");

            CurrencyTransport ct = new CurrencyTransport();
            ct.CurrencyCode = m.CurrencyCode;
            ct.Amount = m.Amount;
            return ct;
        }

        public static explicit operator Money2(string s)
        {
            return ToMoney(s);
        }

        #endregion Implementation

        /////////////////////////////////////////////////////////////
        // Operations

        public string Format(bool bWithBrackets)
        {
            if (Amount >= 0) return string.Format("{0} {1:#,0.00} ", this.CurrencySymbol, this.Amount);
            return string.Format((bWithBrackets ? "{0} ({1:#,0.00})" : "{0} {1:#,0.00}-"), this.CurrencySymbol, Math.Abs(this.Amount));
        }

        /////////////////////////////////////////////////////////////
        // Static Operations

        public static Money2 ToMoney(string value)
        {
            return ToMoney(value, string.Empty);
        }
        public static Money2 ToMoney(string value, string defaultCurrency)
        {
            if (string.IsNullOrWhiteSpace(value)) return null;

            bool minus = false;
            decimal amount = 0;
            string code = null;
            string work = value.Trim();

            if (work.Contains("-"))
            {
                work = work.Remove(work.LastIndexOf("-"), 1).Trim();
                minus = true;
            }
            else if (work.Contains("(") && work.Contains(")"))
            {
                work = work.Remove(work.LastIndexOf("("), 1);
                work = work.Remove(work.LastIndexOf(")"), 1).Trim();
                minus = true;
            }

            int i = 0;
            while (i < work.Length && !".0123456789".Contains(work[i].ToString())) i++;
            if (i > 0)
            {
                code = work.Substring(0, i).Trim().ToUpper();
                work = work.Remove(0, i);
            }

            if (string.IsNullOrWhiteSpace(code)) code = (!string.IsNullOrWhiteSpace(defaultCurrency)) ? defaultCurrency : DefaultCurrency;
            if (!IsValidCurrencySymbol(code))
            {
                throw new Exception(string.Format("Invalid currency symbol {0}.", code));
            }

            if (!string.IsNullOrWhiteSpace(work))
            {
                try { amount = Math.Round((Convert.ToDecimal(work)), 2); }
                catch
                {
                    throw new Exception(string.Format("Invalid monetary value {0}.", value));
                }
                if (minus && amount > 0) amount = -amount;
            }

            Money2 money = new Money2(amount, code);
            return money;
        }

        public static bool IsValidCurrencySymbol(string code)
        {
            if (code == GBP_Symbol || code == USD_Symbol || code == Euro_Symbol || code == Yen_Symbol || code == Mixed_Code)
                return true;
            return IsValidCurrencyCode(code.ToUpper());
        }

        public static bool IsValidCurrencyCode(string code)
        {
            //TODO replace this code with a database currency table validation check;
            if (code.Length == 3) return true;
            return false;
        }

        public static string CurrencySymbolToCode(string symbol)
        {
            if (string.IsNullOrWhiteSpace(symbol)) return DefaultCurrency;

            string code = symbol.ToUpper().Trim();

            if (code == GBP_Symbol) code = GBP_Code;
            else if (code == USD_Symbol) code = USD_Code;
            else if (code == Euro_Symbol) code = Euro_Code;
            else if (code == Yen_Symbol) code = Yen_Code;

            if (!IsValidCurrencyCode(code))
                code = Unknown_Code;
            return code;
        }

    }
}
